package com.itcoon.transform.base.register.annotated;

import com.itcoon.transform.base.annotation.AssembleModel;
import com.itcoon.transform.base.annotation.AssembleModelProperty;
import com.itcoon.transform.base.definition.ModelDefinition;
import com.itcoon.transform.base.definition.ModelPropertyDefinition;
import com.itcoon.transform.base.utils.TransformServiceUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Optional;

public class AnnotatedClazzAssembleModelRegister implements AnnotatedAssembleModelRegister {

    public static final Logger log = LoggerFactory.getLogger(AnnotatedClazzAssembleModelRegister.class);

    @Override
    public boolean support(Class<?> targetClass) {
        return !targetClass.isInterface() && targetClass.getAnnotation(AssembleModel.class) != null;
    }

    @Override
    public void register(ModelDefinition modelDefinition) throws IntrospectionException {
        Class<?> targetClass = modelDefinition.getTargetClass();
        AssembleModel assembleModel = targetClass.getAnnotation(AssembleModel.class);
        BeanInfo targetBeanInfo = Introspector.getBeanInfo(targetClass);
        Class<?> originClass = assembleModel.from();
        modelDefinition.setOriginClass(originClass);
        PropertyDescriptor[] targetPropertyDescriptors = targetBeanInfo.getPropertyDescriptors();
        for (PropertyDescriptor targetPropertyDescriptor : targetPropertyDescriptors) {
            if(targetPropertyDescriptor.getName().equals("class")){
                continue;
            }
            ModelPropertyDefinition mpd = new ModelPropertyDefinition(modelDefinition);
            Field field = ReflectionUtils.findField(targetClass, targetPropertyDescriptor.getName());
            Optional<AssembleModelProperty> annotationOp = Optional.empty();
            if(field != null){
                annotationOp = Optional.ofNullable(field.getAnnotation(AssembleModelProperty.class));
            }
            Method method;
            if(!annotationOp.isPresent() && (method = targetPropertyDescriptor.getReadMethod())!=null){
                annotationOp = Optional.ofNullable(method.getAnnotation(AssembleModelProperty.class));
            }
            mpd.setAnnotated(annotationOp.isPresent());
            mpd.setTargetGetter(targetPropertyDescriptor.getReadMethod());
            mpd.setTargetAttribute(targetPropertyDescriptor.getName());
            mpd.setTargetPropertyType(targetPropertyDescriptor.getPropertyType());

            String originPropertyAttribute;
            if(mpd.getAnnotated()){
                AssembleModelProperty assembleModelProperty = annotationOp.get();
                originPropertyAttribute = StringUtils.isEmpty(assembleModelProperty.mapValue()) ? null : assembleModelProperty.mapValue();
                if(originPropertyAttribute == null && field == null){
                    modelDefinition.getPropertyDefinitions().add(mpd);
                    continue;
                }else if(originPropertyAttribute == null){
                    originPropertyAttribute = mpd.getTargetAttribute();
                }
            }else{
                originPropertyAttribute = mpd.getTargetAttribute();
            }
            mpd.setTargetSetter(targetPropertyDescriptor.getWriteMethod());
            mpd.setOriginAttribute(originPropertyAttribute);

            //如果目标类没有setter，则跳到下一个
            if(mpd.getTargetSetter() == null){
                modelDefinition.getPropertyDefinitions().add(mpd);
                continue;
            }

            Method originGetter = TransformServiceUtils.findGetterFromValue(originClass, originPropertyAttribute);
            mpd.setOriginGetter(originGetter);
            if(originGetter != null){
                Class<?> originPropertyType = originGetter.getReturnType();
                mpd.setOriginPropertyType(originPropertyType);
            }
            modelDefinition.getPropertyDefinitions().add(mpd);
        }
    }
}
